package aiingames.simulation.server;

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;

import aiingames.Config;
import aiingames.simulation.agent.Agent;
import aiingames.simulation.agent.AgentAction;
import aiingames.simulation.agent.AgentAction.SimpleAction;
import aiingames.simulation.agent.Ball;
import aiingames.simulation.agent.RLAgent;
import aiingames.simulation.agent.State;
import aiingames.simulation.gui.Gui;
import aiingames.simulation.io.EnvironmentDistancesReader;
import aiingames.simulation.server.physics.AgentMovement;
import aiingames.simulation.server.physics.AgentParticle;
import aiingames.simulation.server.physics.BallParticle;
import aiingames.simulation.server.physics.CircularParticleInteractionModule;
import aiingames.simulation.server.physics.EnvironmentDistances;
import aiingames.simulation.server.physics.EnvironmentForceModule;
import aiingames.simulation.server.physics.Force;
import aiingames.simulation.server.physics.GravityForceModule;
import aiingames.simulation.server.physics.Particle;
import aiingames.simulation.server.physics.StaticEnvironmentDistancesField;
import aiingames.simulation.server.physics.WindForceModule;

import com.vividsolutions.jts.geom.Coordinate;


public class Server {
	
	Map<Particle,Agent> pAMapping = new HashMap<Particle, Agent>();
	Map<Agent,Particle> aPMapping = new HashMap<Agent, Particle>();
	private String env;
	private EnvironmentForceModule envForceModule;
	private CircularParticleInteractionModule particleInteractionModule;
	private double simSteps;
	private AgentMovement agentMovement;
	private Gui gui;
	private StaticEnvironmentDistancesField envDists;
	private GravityForceModule gravity;
	private WindForceModule wind;
	private Particle ag;
	boolean ballInAir = true;
	
	AgentAction agentAction = null;
	State stateBeforeAction;
	SimpleAction simpleAction = null;
	
	
	public Server(String env, double simSteps) {
		this.env = env;
		this.simSteps = simSteps;
		init();
	}
	
	public void run() {
		initForces();
		for (int i = 0; i < this.simSteps; i++) {
			updatedForces(i);
			if (!moveParticles()) {
				break;
			}
		}
	}
	
	
	private boolean moveParticles() {
		for (Particle p : this.aPMapping.values()) {
			Force f = p.getForce();
			Coordinate oldPos = p.getPosition();
			f.update();
			
			validateVelocity(p);
			
			double vx = f.getVx();
			double vy = f.getVy();
			
			Coordinate newPos = new Coordinate(oldPos.x + f.getVx()* Config.TIME_STEP_SIZE, oldPos.y + f.getVy()* Config.TIME_STEP_SIZE, 0);
			p.setCurrentVelocity(vx,vy);
			
			p.moveToPosition(newPos);
			
			if (p instanceof BallParticle) {
				if (p.getPosition().y > 6.9) {
					ballInAir = false;
					if (p.getPosition().distance(ag.getPosition()) < 0.1) {
						System.out.println("game over");
					}

					return false;
				}
			}
		}
		if (this.gui != null ) {
			this.gui.update(aPMapping.values());
		}
		return true;
	}

	private void validateVelocity(Particle p) {
		if (p instanceof AgentParticle) {
			p.getForce().setVy(0);
		}
		
		Force f = p.getForce();
		double v = Math.sqrt(Math.pow(f.getVx(), 2)+ Math.pow(f.getVy(), 2));
		double scale = v / p.getMaxVelocity();
		if (scale > 1) {
			f.setVx(f.getVx()/scale);
			f.setVy(f.getVy()/scale);
		}
	}

	private void updatedForces(int i) {

		this.particleInteractionModule.update(i * Config.TIME_STEP_SIZE); 
		for (Particle p : this.aPMapping.values()) {
			this.envForceModule.run(p);
			this.particleInteractionModule.run(p);
			this.gravity.run(p);
			this.wind.run(p);
		}

		for (Agent a : this.pAMapping.values()) {
			
			if (a instanceof RLAgent) {
				//System.out.println("State after action "+stateBeforeAction);

				//Policy-Funktion (getSimpleAction) aufrufen + Action zwischenspeichern
				simpleAction = ((RLAgent)a).getSimpleAction(this);
				
				System.out.println("beginn "+((RLAgent)a).getActualState().toString());
				
				if (stateBeforeAction!=null)
					System.out.println(stateBeforeAction.getSuccessors());
				
				//State vor der Aktion festhalten
				stateBeforeAction = new State(((RLAgent)a).getActualState());
				((RLAgent)a).putState(stateBeforeAction);
				
				//agentAction festlegen
				agentAction = AgentAction.translateSimpleAction(simpleAction);

			
			} else {
				agentAction = a.perform(this);
			}

			//Agent (Ball ODER RLAgent) bewegen
			this.agentMovement.updateMovement(agentAction, this.aPMapping.get(a));

			
			if (a instanceof RLAgent) {

				//System.out.println("state before "+stateBeforeAction);
				
				//Dem vorigen State den Successor geben
				stateBeforeAction.setSuccessor(simpleAction, stateBeforeAction);
				
				
				//Check if ball is in air in the actual state
				//((RLAgent) a).setBallInAir(ballInAir);
				
				//Value des aktuellen States aktualisieren und umliegende ebenfalls
				((RLAgent) a).updateValues(stateBeforeAction);
				
				//Umliegende Values 
				//((RLAgent) a).updateValues(utility);
			}
		}
	}

	private void init() {
		EnvironmentDistancesReader envReader = new EnvironmentDistancesReader();
		try {
			envReader.setValidating(false);
			envReader.parse(this.env);
		} catch (SAXException e) {
			throw new RuntimeException(e);
		} catch (ParserConfigurationException e) {
			throw new RuntimeException(e);
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
		this.envDists = envReader.getEnvDistField();
		
		
	}


	private void initForces() {
		
		this.envForceModule = new EnvironmentForceModule(this.envDists);
		this.particleInteractionModule = new CircularParticleInteractionModule(this);
		this.gravity = new GravityForceModule();
		this.agentMovement = new AgentMovement();
		this.wind = new WindForceModule();
	}

	public void addAgent(Agent agent, Coordinate location) {
		Particle p = null;
		if (agent instanceof Ball) {
			p = new BallParticle(location);
		} else {
			p = new AgentParticle(location);
			this.ag = p;
		}
		
		
		this.pAMapping.put(p, agent);
		this.aPMapping.put(agent, p);
	}
	
	public void resetAgent(Agent agent, Coordinate location) {
		Particle p = this.aPMapping.get(agent);
		p.moveToPosition(location);
		p.setCurrentVelocity(0, 0);
		p.getForce().reset();
	}
	
	public Collection<Particle> getParticles() {
		return this.aPMapping.values();
	}

	public void addGui(Gui gui) {
		this.gui = gui;
		
	}

	public Coordinate getAgentLocation(Agent agent) {
		return this.aPMapping.get(agent).getPosition();
	}


	public List<Coordinate> getNeighboringObjects(Coordinate location) {
		EnvironmentDistances ed = this.envDists.getEnvironmentDistances(location);
		return ed.getObjects();
	}

	public Coordinate getAgentVelocityVector(Agent agent) {
		double vx = this.aPMapping.get(agent).getVx();
		double vy = this.aPMapping.get(agent).getVy();
		return new Coordinate(vx,vy);
	}

}
